﻿/**
 * Peter
 * Created by: Peter Development Team
 *    http://peter.codeplex.com/
 * 
 * GNU General Public License version 2 (GPLv2)
 *    http://peter.codeplex.com/license
 *
 *  This code is provided on an AS IS basis, with no WARRANTIES,
 *  CONDITIONS or GUARANTEES of any kind.
 *  
 **/

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Xml;
using ICSharpCode.NRefactory.TypeSystem;
using Microsoft.Build.BuildEngine;
using Peter.Common;
using Peter.Common.Code;
using Peter.Common.Icons;
using Peter.Common.MainMenu;
using Peter.Common.Tree;
using Project = CWDev.SLNTools.Core.Project;
using CSharpParser = ICSharpCode.NRefactory.CSharp.CSharpParser;

namespace Peter.Net
{
   /// <summary>
   /// C# Project
   /// </summary>
   public class CSharpProject : PeterProjectItem
   {
      private XmlNodeList m_ExpandedNodes;
      private readonly List <IUnresolvedFile> m_UnresolvedFiles = new List <IUnresolvedFile> ();
      private BaseCodeCompletion m_CodeCompletion;

      /// <summary>
      /// Default constructor.
      /// </summary>
      public CSharpProject (ModelTreeViewItem parent, string path)
         : base (parent, new FileInfo(path))
      {
         this.Init();
      }

      /// <summary>
      /// Initializes a new c sharp project.
      /// </summary>
      /// <param name="parent">Parent item</param>
      /// <param name="solution">Parent Solution.</param>
      /// <param name="project">Project to use.</param>
      public CSharpProject (ModelTreeViewItem parent, SolutionProject solution, Project project) : base (parent, new FileInfo(project.FullPath))
      {
         this.Solution = solution;
         this.Init ();
      }

      private void Init ()
      {
         this.CanBeCurrentProject = true;
         this.Icon = MainMenuIcon.Csharp;
         this.IconSize = IconSize.Smallest;
         this.BorderBrush = Brushes.Green;
         this.BorderThickness = new Thickness (1);
         this.IconMargin = new Thickness (0, 1, 3, 1);
         this.IconPadding = new Thickness (3, 2, 3, 2);
      }

      /// <summary>
      /// Gets the solution.
      /// </summary>
      public SolutionProject Solution { get; private set; }

      /// <summary>
      /// Gets the directory of the project.
      /// </summary>
      public string ProjectDirectory { get; private set; }

      /// <summary>
      /// Gets the code completion for the project.
      /// </summary>
      public override BaseCodeCompletion CodeCompletion
      {
         get { return this.Solution.CodeCompletion; }
         set { }
      }

      /// <summary>
      /// Opens a new c sharp project from the given path.
      /// </summary>
      public  async Task<bool> Open ()
      {
         this.m_ExpandedNodes = null;
         if (System.IO.File.Exists (this.File.FullName + ".pdat"))
         {
            var xDoc = new XmlDocument ();
            xDoc.Load (this.File.FullName + ".pdat");
            this.m_ExpandedNodes = xDoc.GetElementsByTagName ("expanded");
         }

         Microsoft.Build.BuildEngine.Project project = new Microsoft.Build.BuildEngine.Project ();
         project.Load (this.File.FullName);
         this.Text = project.FullFileName.Substring (project.FullFileName.LastIndexOf ('\\') + 1);
         this.Text = this.Text.Substring (0, this.Text.LastIndexOf ('.'));
         this.ProjectDirectory = project.FullFileName.Substring (0, project.FullFileName.LastIndexOf ('\\') + 1);
         if (project.ItemGroups != null)
         {
            foreach (BuildItemGroup buildItemGroup in project.ItemGroups)
            {
               foreach (BuildItem item in buildItemGroup)
               {
                  if (IsItemSupported (item))
                     await this.AddProjectItem (item);
               }
            }
         }
         this.Solution.CodeCompletion.AddFiles (this.m_UnresolvedFiles);
         return true;
      }

      /// <summary>
      /// Adds the given item to the project.
      /// </summary>
      /// <param name="item">Item to add.</param>
      private async Task AddProjectItem (BuildItem item)
      {
         var pathSplit = item.Include.Split ('\\');
         if (item.Name == "Reference")
            pathSplit = new[] { "Reference", item.Include };
         var fullPath = this.ProjectDirectory + item.Include;
         var parent = (ModelTreeViewItem) this;
         if (item.HasMetadata ("Link"))
         {
            var link = item.GetMetadata ("Link");
            pathSplit = link.Split ('\\');
         }
         var folderPath = this.ProjectDirectory;
         for (var a = 0; a < pathSplit.Length - 1; a++)
         {
            folderPath += pathSplit[a] + "\\";
            parent = await AddFolder (folderPath, parent);
         }
         if (item.HasMetadata ("DependentUpon"))
         {
            var dependent = item.GetMetadata ("DependentUpon");
            foreach (var child in parent.Children)
            {
               if (child.Text == dependent)
               {
                  child.Children.Add (new PeterProjectItem (child, new FileInfo (fullPath)) { ParentProject = this });
                  return;
               }
            }
            var parentDep = new PeterProjectItem (parent, new FileInfo (folderPath + dependent));
            parentDep.Children.Add (new PeterProjectItem (parentDep, new FileInfo (fullPath)) { ParentProject = this });
            await AddItem (parent, parentDep);
         }
         else
            await AddItem (parent, new PeterProjectItem (parent, new FileInfo (fullPath)) { ParentProject = this });
      }

      private async Task AddItem (ModelTreeViewItem parent, PeterProjectItem child)
      {
         if (!child.IsDirectory)
         {
            if (child.File.Extension.ToLower () == ".cs")
            {
               var fileData = System.IO.File.ReadAllText (child.File.FullName);
               var syntaxTree = await Task.Run(() => new CSharpParser ().Parse (fileData, child.File.FullName));
               syntaxTree.Freeze ();
               this.m_UnresolvedFiles.Add(syntaxTree.ToTypeSystem ());
            }
         }

         if (parent.Children.Count == 0)
            parent.Children.Add (child);
         else
         {
            int index = 0;
            await Task.Run(() =>
            {
               foreach (var treeViewItem in parent.Children.Cast <PeterProjectItem> ())
               {
                  if (child.IsDirectory)
                  {
                     if (treeViewItem.IsDirectory)
                     {
                        var rtn = System.String.Compare (treeViewItem.File.FullName,
                           child.File.FullName,
                           StringComparison.CurrentCultureIgnoreCase);
                        if (rtn == -1)
                           index++;
                        else
                           break;
                     }
                     else
                        break;
                  }
                  else
                  {
                     if (treeViewItem.IsDirectory)
                        index++;
                     else
                     {
                        var rtn = System.String.Compare (treeViewItem.Text, child.Text,
                           StringComparison.CurrentCultureIgnoreCase);
                        if (rtn == -1)
                           index++;
                        else if (rtn == 0)
                           return; // Dont add, it's already there
                        else
                           break;
                     }
                  }

               }
            });
            parent.Children.Insert (index, child);
         }

         if (this.m_ExpandedNodes != null)
         {
            foreach (XmlNode node in this.m_ExpandedNodes)
            {
               if (node.Attributes != null && node.Attributes["path"].Value == child.File.FullName)
               {
                  child.IsExpanded = true;
                  break;
               }
            }
         }
      }

      /// <summary>
      /// Adds the given folder to the project.
      /// </summary>
      /// <param name="folderPath">The full path to the folder.</param>
      /// <param name="parent">The folder's parent.</param>
      /// <returns>The folder item.</returns>
      private async Task<ModelTreeViewItem> AddFolder (string folderPath, ModelTreeViewItem parent)
      {
         foreach (var child in parent.Children.Cast<PeterProjectItem>())
         {
            if (child.File.FullName == folderPath)
               return child;
         }
         var folderItem = new PeterProjectItem (parent, new DirectoryInfo (folderPath)) { ParentProject = this };
         await AddItem (parent, folderItem);
         return folderItem;
      }

      /// <summary>
      /// Checks to see if the given build item is supported or not.
      /// </summary>
      /// <param name="item">Build item to check.</param>
      /// <returns>True if item is supported, otherwise false.</returns>
      private static bool IsItemSupported (BuildItem item)
      {
         return item.Name == "None" ||
            item.Name == "Compile" ||
            item.Name == "Content" ||
            item.Name == "EmbeddedResource" ||
            item.Name == "CodeAnalysisDictionary" ||
            item.Name == "ApplicationDefinition" ||
            item.Name == "Page" ||
            item.Name == "Resource" ||
            item.Name == "SplashScreen" ||
            item.Name == "DesignData" ||
            item.Name == "EntityDeploy" ||
            item.Name == "XamlAppDef" ||
            item.Name == "Fakes";
      }

      /// <summary>
      /// Occurs when the Peter application is closing.
      /// </summary>
      public override void PeterClosing ()
      {
         if (System.IO.File.Exists (this.File.FullName + "_temp"))
            System.IO.File.Delete (this.File.FullName + "_temp");

         try
         {
            using (var writer = new XmlTextWriter (this.File.FullName + "_temp", Encoding.Unicode))
            {
               writer.Indentation = 3;
               writer.Formatting = Formatting.Indented;
               writer.WriteStartDocument ();
               this.SaveStructure (writer);
               writer.WriteEndDocument ();
            }
            System.IO.File.Delete (this.File.FullName + ".pdat");
            System.IO.File.Copy (this.File.FullName + "_temp", this.File.FullName + ".pdat");
            System.IO.File.Delete (this.File.FullName + "_temp");
         }
         catch (Exception ex)
         {
            Console.Error.WriteLine (ex.Message);
         }
      }

      /// <summary>
      /// Saves the project structure.
      /// </summary>
      /// <param name="writer">Xml output.</param>
      private void SaveStructure (XmlWriter writer)
      {
         writer.WriteStartElement ("structure");
         foreach (var editableTreeViewItem in this.Children.Where (ti => ti.IsExpanded))
         {
            var item = (FileTreeViewItem)editableTreeViewItem;
            SaveExpandedNodes (item, writer);
         }
         writer.WriteEndElement ();
      }

      /// <summary>
      /// Recursively saves the expanded nodes in the project's structure.
      /// </summary>
      /// <param name="treeItem">Tree item to save.</param>
      /// <param name="writer">Xml output.</param>
      private static void SaveExpandedNodes (FileTreeViewItem treeItem, XmlWriter writer)
      {
         writer.WriteStartElement ("expanded");
         writer.WriteAttributeString ("path", treeItem.File.FullName);
         foreach (var editableTreeViewItem in treeItem.Children.Where (ti => ti.IsExpanded))
         {
            var item = (FileTreeViewItem)editableTreeViewItem;
            SaveExpandedNodes (item, writer);
         }
         writer.WriteEndElement ();
      }
   }
}
